#include "GeneralTextViewAdaptor.h"
#include "GeneralTextPlugin.h"
#include "GeneralTextWidget.h"

#include "simodo/module/ModuleCollector_interface.h"
#include "simodo/variable/json/Serialization.h"
#include "simodo/inout/convert/functions.h"
#include "simodo/shell/access/LspAccess_interface.h"

#include <QtPrintSupport/QtPrintSupport>
#include <QFileInfo>
#include <QComboBox>

using namespace simodo;

GeneralTextViewAdaptor::GeneralTextViewAdaptor(shell::Access_interface & shell_access, 
                                               GeneralTextPlugin * plugin,
                                               std::shared_ptr<shell::DocumentAdaptor_interface> document_adaptor, 
                                               const QString & path_to_file)
    : _shell(shell_access), _plugin(plugin), _document_adaptor(document_adaptor), _path_to_file(path_to_file)
{
    _edit_params = loadEditParameters();

    if (!document_adaptor)
    {
        std::shared_ptr<shell::DocumentAdaptor_interface> doc_adapter = plugin->createDocumentAdaptor(_shell);
        _document_adaptor = doc_adapter;
    }

    GeneralTextDocumentAdaptor * mine_doc_adaptor = dynamic_cast<GeneralTextDocumentAdaptor *>(_document_adaptor.get());

    if (mine_doc_adaptor) {
        _widget = new GeneralTextWidget(*this, &(mine_doc_adaptor->document()));
        _current_editor = _editor = _widget->code_edit();
        mine_doc_adaptor->setSaveBackupMode(params().save_backup_file);
    }
}

GeneralTextViewAdaptor::~GeneralTextViewAdaptor()
{
    /// @note Удаляем неиспользуемые данные.
    /// Сейчас информация о подсветке загружается каждый раз, когда открывается документ
    /// в редакторе. Хотя можно было бы это не делать при повторном открытии, т.к. 
    /// адаптер документа может быть не выгружен.
    GeneralTextDocumentAdaptor * mine_doc_adaptor = dynamic_cast<GeneralTextDocumentAdaptor *>(_document_adaptor.get());
    if (mine_doc_adaptor)
        mine_doc_adaptor->setHighlighter(nullptr);
}

void GeneralTextViewAdaptor::setCurrentEditor(CodeEdit *editor)
{
    _current_editor = editor ? editor : _editor;
}

void GeneralTextViewAdaptor::forceCompletion()
{
    sendDidChangeNotification();
}

QWidget * GeneralTextViewAdaptor::document_widget()
{
    return _widget;
}

shell::Document_plugin *GeneralTextViewAdaptor::plugin()
{
    return _plugin;
}

void GeneralTextViewAdaptor::readyToWork(QWidget *widget)
{
    GeneralTextWidget *gtw = qobject_cast<GeneralTextWidget *>(widget);
    if (!gtw)
    {
        _shell.sendGlobal("GeneralTextViewAdaptor::readyToWork: " + tr("Unrecognizable widget"),
                          shell::MessageSeverity::Error);
        return;
    }

    CodeEdit *editor = gtw->code_edit();

    if (gtw == _widget) {
        emit gotZoom(_edit_params.font_zero_size);

        GeneralTextDocumentAdaptor * mine_doc_adaptor = dynamic_cast<GeneralTextDocumentAdaptor *>(_document_adaptor.get());

        if (mine_doc_adaptor && _edit_params.syntax_highlight && !_highlight_data.tokenizers.empty()) {
            Highlighter * highlighter = new Highlighter(&(mine_doc_adaptor->document()), 
                                                        _highlight_data, _current_editor->palette(),
                                                        mine_doc_adaptor->diagnostics(),
                                                        mine_doc_adaptor->semantic_tokens(),
                                                        params().semantic_postorder
                                                            ? Highlighter::SemanticFormatOrder::Post
                                                            : Highlighter::SemanticFormatOrder::Early);

            mine_doc_adaptor->setHighlighter(highlighter); 
        }
        _shell.notifyContentChanged(_path_to_file);
    }
    else {
        editor->setReadOnly(_editor->isReadOnly());
        emit gotDocumentSymbol();
        emit gotZoom(_editor->font().pointSize());
    }

    gtw->readyToWork();

    GeneralTextDocumentAdaptor * mine_doc_adaptor = dynamic_cast<GeneralTextDocumentAdaptor *>(_document_adaptor.get());

    if (mine_doc_adaptor)
        editor->resetHighlighter(mine_doc_adaptor->highlighter());

    if (gtw == _widget) {
        QFont f = _editor->font();
        QToolTip::setFont(f);

        connect(_editor->document(), &QTextDocument::contentsChanged, this, &GeneralTextViewAdaptor::markModification);
        connect(_editor->document(), &QTextDocument::contentsChange, this,
            [this](int /*position*/, int /*charsRemoved*/, int /*charsAdded*/)
            {
                // shell_access().sendGlobal(QString("QTextDocument::contentsChange(%1, %2, %3)")
                //                         .arg(position)
                //                         .arg(charsRemoved)
                //                         .arg(charsAdded));
                killTimer(_lsp_change_delay_timer);
                _lsp_change_delay_timer = startTimer(_editor->params().lsp_change_delay_mills);

                killTimer(_autosave_change_delay_timer);
                _autosave_change_delay_timer = startTimer(_editor->params().autosave_change_delay_mills);
            });
    }
}

QWidget *GeneralTextViewAdaptor::copyWidget()
{
    return new GeneralTextWidget(*this, _editor->document());
}

QString GeneralTextViewAdaptor::loadFile(const QString &file_name)
{
    Q_ASSERT(_document_adaptor);
    QString error_string = _document_adaptor->loadFile(file_name);
    QTextCursor cursor = _editor->textCursor();
    cursor.movePosition(QTextCursor::Start);
    _editor->setTextCursor(cursor);
    return error_string;
}

void GeneralTextViewAdaptor::setReadOnly()
{
    _editor->setReadOnly(true);
}

void GeneralTextViewAdaptor::print(QPrinter *printer)
{
    _editor->print(printer);
}

void GeneralTextViewAdaptor::zoomIn()
{
    emit gotZoom(_editor->font().pointSize() + 1);
    QFont f = _editor->font();
    QToolTip::setFont(f);

    int scaling_percent = qRound(static_cast<double>(f.pointSize()) / params().font_zero_size * 100.0);
    _shell.displayStatistics(QString::number(scaling_percent)+"%");
}

void GeneralTextViewAdaptor::zoomOut()
{
    emit gotZoom(_editor->font().pointSize() - 1);
    QFont f = _editor->font();
    QToolTip::setFont(f);

    int scaling_percent = qRound(static_cast<double>(f.pointSize()) / params().font_zero_size * 100.0);
    _shell.displayStatistics(QString::number(scaling_percent)+"%");
}

void GeneralTextViewAdaptor::zoomZero()
{
    emit gotZoom(params().font_zero_size);
    QFont f = _editor->font();
    QToolTip::setFont(f);

    int scaling_percent = qRound(static_cast<double>(f.pointSize()) / params().font_zero_size * 100.0);
    _shell.displayStatistics(QString::number(scaling_percent)+"%");
}

QPair<int, int> GeneralTextViewAdaptor::getLineCol() const
{
    QTextCursor cursor = _editor->textCursor();
    return {cursor.blockNumber() + 1, cursor.columnNumber() + 1};
}

void GeneralTextViewAdaptor::setLineCol(QPair<int, int> line_col)
{
    _editor->setLineCol(line_col);
}

QString GeneralTextViewAdaptor::getSampleTextToFind() const
{
    return _editor->textCursor().selectedText();
}

bool GeneralTextViewAdaptor::find(const QString &str, QTextDocument::FindFlags options)
{
    _editor->setFindOptions(options);

    if (!_editor->find(str, options)) {
        _editor->moveCursor(QTextCursor::Start);
        if (_editor->find(str, options)) {
            _editor->setFocus();
            return true;
        }
    }
    else {
        _editor->setFocus();
        return true;
    }

    return false;
}

bool GeneralTextViewAdaptor::replace(const QString & /*what*/, const QString & to, QTextDocument::FindFlags options, bool /*all*/)
{
    _editor->setFindOptions(options);

    QTextCursor cur = _editor->textCursor();
    bool        atleast = false;

    if (!cur.selectedText().isEmpty()) {
        cur.insertText(to);
        _editor->setTextCursor(cur);
        _editor->setFocus();
        atleast = true;
    }

    /// \todo Зависает, скоре всего, из-за зацикливания работы сигналов и слотов. Нужно разбираться
    // if (all) {
    //     while(find(what, options)) {
    //         QTextCursor cur = _editor->textCursor();
    //         cur.insertText(to);
    //         _editor->setTextCursor(cur);
    //     }
    // }

    return atleast;
}

void GeneralTextViewAdaptor::markModification()
{
    QWidget *p = _widget;
    while ((p = qobject_cast<QWidget *>(p->parent())) != nullptr)
    {
        QMdiSubWindow *sub = qobject_cast<QMdiSubWindow *>(p);
        if (sub)
        {
            sub->setWindowModified(document_adaptor()->isModified());
            return;
        }
    }
}

void GeneralTextViewAdaptor::nearToClose()
{
    _widget->nearToClose();
}

void GeneralTextViewAdaptor::publishDiagnostics(int version, QVector<shell::Diagnostic> &diagnostics, int checksum)
{
    if (_version != version || !_editor->params().lsp_support)
        return;

    if (_publishDiagnostics_checksum != checksum) {
        _publishDiagnostics_checksum = checksum;

        if (_editor->params().syntax_highlight) {
            GeneralTextDocumentAdaptor * mine_doc_adaptor = dynamic_cast<GeneralTextDocumentAdaptor *>(_document_adaptor.get());

            if (mine_doc_adaptor)
                mine_doc_adaptor->publishDiagnostics(diagnostics);
        }

        emit gotPublishDiagnostics(version);
    }

    shell::LspAccess_interface *lsp = shell().getLspAccess(path_to_file());
    if (lsp) {
        if (params().document_symbols)
            lsp->documentSymbol(path_to_file());
        if (params().semantic_tokens)
            lsp->semanticTokens(path_to_file());
    }
}

void GeneralTextViewAdaptor::hover(const void *sender, const QString &kind, const QString &value)
{
    emit gotHover(sender, kind, value);
}

void GeneralTextViewAdaptor::documentSymbol(QVector<shell::DocumentSymbol> &symbols)
{
    _symbols.swap(symbols);

    std::sort(_symbols.begin(), _symbols.end(),
              [](shell::DocumentSymbol &a, shell::DocumentSymbol &b)
              {
                  return a.range.start() < b.range.start();
              });

    emit gotDocumentSymbol();
}

void GeneralTextViewAdaptor::semanticTokens(std::multimap<int, shell::SemanticToken> &tokens, int checksum)
{
    if (_semanticTokens_checksum != checksum) {
        _semanticTokens_checksum = checksum;

        GeneralTextDocumentAdaptor * mine_doc_adaptor = dynamic_cast<GeneralTextDocumentAdaptor *>(_document_adaptor.get());

        if (mine_doc_adaptor)
            mine_doc_adaptor->semanticTokens(tokens);

        emit gotSemanticTokens();
    }
}

void GeneralTextViewAdaptor::completionItems(const void *sender, QVector<shell::CompletionItem> &completions)
{
    // _shell.sendGlobal(QString("completionItems for %1").arg(reinterpret_cast<uint64_t>(sender)));
    _completions.swap(completions);
    emit gotCompletionItems(sender);
}

CodeEditParameters GeneralTextViewAdaptor::loadEditParameters()
{
    /// @attention Нельзя использовать _editor, т.к. он ещё не создан!

    std::string path_to_edit_setup = _shell.provideDataStorageFolder(_plugin).toStdString();
    std::string edit_setup_file = path_to_edit_setup + "/edit-setup.json";

    variable::Value edit_setup_value;
    variable::loadJson(edit_setup_file, edit_setup_value);
    if (edit_setup_value.type() != variable::ValueType::Object)
    {
        _shell.sendGlobal(tr("Unable to read editor setup file '%1'").arg(QString::fromStdString(edit_setup_file)),
                          shell::MessageSeverity::Error);
        return {};
    }

    return checkCapabilities(prepareLanguageData(loadEditParameters(edit_setup_value.getObject(), {})));
}

CodeEditParameters GeneralTextViewAdaptor::checkCapabilities(CodeEditParameters params)
{
    shell::LspAccess_interface * lsp = _shell.getLspAccess(_path_to_file);
    if (params.lsp_support && lsp) {
        params.lsp_completer    = params.lsp_completer 
                                ? lsp->checkServerCapability(_path_to_file, shell::ServerCapability::completion)
                                : false;
        params.lsp_hover        = params.lsp_hover
                                ? lsp->checkServerCapability(_path_to_file, shell::ServerCapability::hover)
                                : false;
        params.document_symbols = params.document_symbols
                                ? lsp->checkServerCapability(_path_to_file, shell::ServerCapability::documentSymbol)
                                : false;
        params.semantic_tokens  = params.semantic_tokens
                                ? lsp->checkServerCapability(_path_to_file, shell::ServerCapability::semanticTokens)
                                : false;
    }
    else {
        params.lsp_support = false;
        params.lsp_completer = false;
        params.lsp_hover = false;
        params.document_symbols = false;
        params.semantic_tokens = false;
    }

    params.tab_size = qMax(params.tab_size, 1);
    params.tab_size = qMin(params.tab_size, 32);
    params.font_zero_size = qMax(params.font_zero_size, 4);
    params.minimap_font_point_size = qMax(params.minimap_font_point_size, 1);
    params.minimap_font_point_size = qMin(params.minimap_font_point_size, params.font_zero_size);
    params.chars_count_for_completer_popup = qMax(params.chars_count_for_completer_popup, 1);
    params.chars_count_for_completer_popup = qMin(params.chars_count_for_completer_popup, 10);
    params.completer_max_rows = qMax(params.completer_max_rows, 1);
    params.completer_max_rows = qMin(params.completer_max_rows, 30);
    params.cursor_position_change_delay_mills = qMax(params.cursor_position_change_delay_mills, 1);
    params.cursor_position_change_delay_mills = qMin(params.cursor_position_change_delay_mills, 2000);
    params.lsp_change_delay_mills = qMax(params.lsp_change_delay_mills, 1);
    params.lsp_change_delay_mills = qMin(params.lsp_change_delay_mills, 2000);

    return params;
}

CodeEditParameters GeneralTextViewAdaptor::loadEditParameters(std::shared_ptr<simodo::variable::Object> setup,
                                                              CodeEditParameters params)
{
    /// @attention Нельзя использовать _editor, т.к. он ещё не создан!

    const variable::Value &tab_size_value = setup->find(u"tab_size");
    if (tab_size_value.type() == variable::ValueType::Int)
        params.tab_size = tab_size_value.getInt();

    const variable::Value &wrap_lines_value = setup->find(u"wrap_lines");
    if (wrap_lines_value.type() == variable::ValueType::Bool)
        params.wrap_lines = wrap_lines_value.getBool();

    const variable::Value &font_name_value = setup->find(u"font_name");
    if (font_name_value.type() == variable::ValueType::String)
        params.font_name = QString::fromStdU16String(font_name_value.getString());

    const variable::Value &font_names_value = setup->find(u"font_names");
    if (font_names_value.type() == variable::ValueType::Array) {
        for(const variable::Value & value : font_names_value.getArray()->values()) 
            if (value.type() == variable::ValueType::String)
                params.font_names.push_back(QString::fromStdU16String(value.getString()));
    }

    const variable::Value &font_size_value = setup->find(u"font_zero_size");
    if (font_size_value.type() == variable::ValueType::Int)
        params.font_zero_size = font_size_value.getInt();

    const variable::Value &font_default_size_value = setup->find(u"font_default_size");
    if (font_default_size_value.type() == variable::ValueType::Int)
        params.font_default_size = font_default_size_value.getInt();

    const variable::Value &line_number_area_value = setup->find(u"line_number_area");
    if (line_number_area_value.type() == variable::ValueType::Bool)
        params.line_number_area = line_number_area_value.getBool();

    const variable::Value &minimap_value = setup->find(u"minimap");
    if (minimap_value.type() == variable::ValueType::Bool)
        params.minimap = minimap_value.getBool();

    const variable::Value &minimap_font_point_size = setup->find(u"minimap_font_point_size");
    if (minimap_font_point_size.type() == variable::ValueType::Int)
        params.minimap_font_point_size = minimap_font_point_size.getInt();

    const variable::Value &minimap_auto_scaling = setup->find(u"minimap_auto_scaling");
    if (minimap_auto_scaling.type() == variable::ValueType::Bool)
        params.minimap_auto_scaling = minimap_auto_scaling.getBool();

    const variable::Value &auto_indent_value = setup->find(u"auto_indent");
    if (auto_indent_value.type() == variable::ValueType::Bool)
        params.auto_indent = auto_indent_value.getBool();

    const variable::Value &comment_by_slash_value = setup->find(u"comment_by_slash");
    if (comment_by_slash_value.type() == variable::ValueType::Bool)
        params.comment_by_slash = comment_by_slash_value.getBool();

    const variable::Value &tabs_replacing_value = setup->find(u"tabs_replacing");
    if (tabs_replacing_value.type() == variable::ValueType::Bool)
        params.tabs_replacing = tabs_replacing_value.getBool();

    // const variable::Value & truncate_spaces_in_lines_value = setup->find(u"truncate_spaces_in_lines");
    // if (truncate_spaces_in_lines_value.type() == variable::ValueType::Bool)
    //     params.truncate_spaces_in_lines = truncate_spaces_in_lines_value.getBool();

    const variable::Value &use_completer_value = setup->find(u"use_completer");
    if (use_completer_value.type() == variable::ValueType::Bool)
        params.use_completer = use_completer_value.getBool();

    const variable::Value &chars_count_for_completer_popup_value = setup->find(u"chars_count_for_completer_popup");
    if (chars_count_for_completer_popup_value.type() == variable::ValueType::Int)
        params.chars_count_for_completer_popup = chars_count_for_completer_popup_value.getInt();

    const variable::Value &use_lexis_for_completer_value = setup->find(u"use_lexis_for_completer");
    if (use_lexis_for_completer_value.type() == variable::ValueType::Bool)
        params.use_lexis_for_completer = use_lexis_for_completer_value.getBool();

    const variable::Value &lsp_completer_value = setup->find(u"lsp_completer");
    if (lsp_completer_value.type() == variable::ValueType::Bool)
        params.lsp_completer = lsp_completer_value.getBool();

    const variable::Value &ignore_right_side_of_word_value = setup->find(u"ignore_right_side_of_word");
    if (ignore_right_side_of_word_value.type() == variable::ValueType::Bool)
        params.ignore_right_side_of_word = ignore_right_side_of_word_value.getBool();

    const variable::Value &completer_max_rows_value = setup->find(u"completer_max_rows");
    if (completer_max_rows_value.type() == variable::ValueType::Int)
        params.completer_max_rows = completer_max_rows_value.getInt();

    const variable::Value &cursor_position_change_delay_mills_value = setup->find(u"cursor_position_change_delay_mills");
    if (cursor_position_change_delay_mills_value.type() == variable::ValueType::Int)
        params.cursor_position_change_delay_mills = cursor_position_change_delay_mills_value.getInt();

    const variable::Value &syntax_highlight_value = setup->find(u"syntax_highlight");
    if (syntax_highlight_value.type() == variable::ValueType::Bool)
        params.syntax_highlight = syntax_highlight_value.getBool();

    const variable::Value &highlight_data_file_value = setup->find(u"highlight_data_file");
    if (highlight_data_file_value.type() == variable::ValueType::String)
        params.highlight_data_file = QString::fromStdU16String(highlight_data_file_value.getString());

    const variable::Value &lsp_support_value = setup->find(u"lsp_support");
    if (lsp_support_value.type() == variable::ValueType::Bool)
        params.lsp_support = lsp_support_value.getBool();

    const variable::Value &lsp_hover_value = setup->find(u"lsp_hover");
    if (lsp_hover_value.type() == variable::ValueType::Bool)
        params.lsp_hover = lsp_hover_value.getBool();

    const variable::Value &document_symbols_value = setup->find(u"document_symbols");
    if (document_symbols_value.type() == variable::ValueType::Bool)
        params.document_symbols = document_symbols_value.getBool();

    const variable::Value &semantic_tokens_value = setup->find(u"semantic_tokens");
    if (semantic_tokens_value.type() == variable::ValueType::Bool)
        params.semantic_tokens = semantic_tokens_value.getBool();

    const variable::Value &semantic_postorder_value = setup->find(u"semantic_postorder");
    if (semantic_postorder_value.type() == variable::ValueType::Bool)
        params.semantic_postorder = semantic_postorder_value.getBool();

    const variable::Value &hover_for_identifiers_only_value = setup->find(u"hover_for_identifiers_only");
    if (hover_for_identifiers_only_value.type() == variable::ValueType::Bool)
        params.hover_for_identifiers_only = hover_for_identifiers_only_value.getBool();

    const variable::Value &lsp_change_delay_mills_value = setup->find(u"lsp_change_delay_mills");
    if (lsp_change_delay_mills_value.type() == variable::ValueType::Int)
        params.lsp_change_delay_mills = lsp_change_delay_mills_value.getInt();

    const variable::Value & autosave_value = setup->find(u"autosave");
    if (autosave_value.type() == variable::ValueType::Bool)
        params.autosave = autosave_value.getBool();

    const variable::Value & autosave_change_delay_mills_value = setup->find(u"autosave_change_delay_mills");
    if (autosave_change_delay_mills_value.type() == variable::ValueType::Int)
        params.autosave_change_delay_mills = autosave_change_delay_mills_value.getInt();

    const variable::Value & save_backup_file_value = setup->find(u"save_backup_file");
    if (save_backup_file_value.type() == variable::ValueType::Bool)
        params.save_backup_file = save_backup_file_value.getBool();

    /// @todo Не забыть добавить новые параметры редактора для чтения из настроек

    return params;
}

CodeEditParameters GeneralTextViewAdaptor::prepareLanguageData(CodeEditParameters params)
{
    /// @attention Нельзя использовать _editor, т.к. он ещё не создан!

    // Загрузка описания языков
    std::string path_to_language_setup = _shell.provideDataStorageFolder(_plugin).toStdString();

    variable::Value language_setup_value;
    variable::loadJson(path_to_language_setup + "/languages-setup.json", language_setup_value);
    if (language_setup_value.type() != variable::ValueType::Array)
    {
        return params;
    }

    // Поиск языка
    std::shared_ptr<variable::Array> language_setup_array = language_setup_value.getArray();
    std::u16string language;

    for (const variable::Value &setup_value : language_setup_array->values())
    {
        if (setup_value.type() != variable::ValueType::Object)
            continue;

        std::shared_ptr<variable::Object> setup = setup_value.getObject();
        const variable::Value &extensions_value = setup->find(u"extensions");

        if (extensions_value.type() != variable::ValueType::Array)
            continue;

        for (const variable::Value &ext_value : extensions_value.getArray()->values())
        {
            if (ext_value.type() != variable::ValueType::String)
                continue;

            const std::u16string &ext = ext_value.getString();
            if (ext.empty())
                continue;

            bool found = false;
            if (ext[0] == u'.')
                found = u"." + QFileInfo(_path_to_file).suffix().toStdU16String() == ext;
            else
                found = QFileInfo(_path_to_file).fileName().toStdU16String() == ext;

            if (!found)
                continue;

            const variable::Value &language_value = setup->find(u"language");
            if (language_value.type() != variable::ValueType::String)
                continue;

            language = language_value.getString();

            // Тут загружаем прочие параметры языка, связанные с редактором
            params = loadEditParameters(setup, params);
            break;
        }

        if (!language.empty())
            break;
    }

    if (_edit_params.syntax_highlight) {
        // Загрузка токенайзеров
        module::ModuleCollector_interface *collector = _shell.module_collector();
        if (!collector)
            return params;

        std::shared_ptr<variable::Array> object_array = collector->produceObjectsByMask("tokenizer");
        if (!object_array)
            return params;

        std::u16string path_to_tokenizers = _shell.provideDataStorageFolder("tokenizers").toStdU16String();

        for (const variable::Value & object_value : object_array->values())
            if (object_value.type() == variable::ValueType::Object)
            {
                std::shared_ptr<variable::Object> object = object_value.getObject();

                variable::Value setup_result = object->invoke(u"setup", {{u"path_to_data", path_to_tokenizers},
                                                                        {u"language", language}});
                if (setup_result.type() != variable::ValueType::String || !setup_result.getString().empty())
                    continue;

                // int                     priority       = 10;
                // const variable::Value & priority_value = object->find(u"priority");
                // if (priority_value.type() == variable::ValueType::Int)
                //     priority = priority_value.getInt();

                /// @todo У токенайзера может быть несколько специализаций.
                /// Т.е. тип может быть не только строкой, но и массивом строк.
                std::u16string specialization;
                const variable::Value &specialization_value = object->find(u"specialization");
                if (specialization_value.type() == variable::ValueType::String)
                    specialization = specialization_value.getString();

                if (specialization.empty())
                    _highlight_data.tokenizers.push_back(object);
                else
                    _highlight_data.tokenizers_spec.push_back({object, specialization});

                variable::Value comments_result = object->invoke(u"comments", {});
                if (comments_result.type() == variable::ValueType::Array)
                {
                    auto comments = comments_result.getArray();
                    if (comments->values().size() > 0 && comments->values()[0].type() == variable::ValueType::String)
                        params.line_comment_start_chars = QString::fromStdU16String(comments->values()[0].getString());
                    if (comments->values().size() > 1 && comments->values()[1].type() == variable::ValueType::String)
                        params.comment_begin_chars = QString::fromStdU16String(comments->values()[1].getString());
                    if (comments->values().size() > 2 && comments->values()[2].type() == variable::ValueType::String)
                        params.comment_finish_chars = QString::fromStdU16String(comments->values()[2].getString());

                    if (params.comment_finish_chars.isEmpty())
                        params.comment_begin_chars = "";
                }

                // Загрузка информации для комплитера

                variable::Value symbols_result = object->invoke(u"symbols", {});
                if (symbols_result.type() != variable::ValueType::Array)
                    continue;

                std::shared_ptr<variable::Array> symbol_array = symbols_result.getArray();
                for (const variable::Value &v : symbol_array->values())
                {
                    if (v.type() != variable::ValueType::Object)
                        continue;
                    std::shared_ptr<variable::Object> symbol_object = v.getObject();
                    const variable::Value &symbol_value = symbol_object->find(u"symbol");
                    const variable::Value &type_value = symbol_object->find(u"type");
                    if (symbol_value.type() != variable::ValueType::String || type_value.type() != variable::ValueType::String)
                        continue;

                    std::u16string symbol = symbol_value.getString();
                    std::u16string type = type_value.getString();
                    if (symbol.empty() || type.empty())
                        continue;

                    _lexis_symbols.push_back(QString::fromStdU16String(symbol));
                }
            }

        // Загрузка лексики
        // if (!language.empty()) {
        //     QDir                             path_to_lexis(QString::fromStdU16String(path_to_tokenizers)
        //                                                    +"/lexis/"
        //                                                    +QString::fromStdU16String(language)
        //                                                    +".json");
        //     simodo::inout::LexicalParameters lexis;
            
        //     if (simodo::variable::loadLexicalParameters(path_to_lexis.absolutePath().toStdString(),lexis))
        //         _lexis = std::make_shared<simodo::inout::LexicalParameters>(lexis);
        // }

        // Загрузка выделений
        std::string path_to_highlights = _shell.provideDataStorageFolder("highlights").toStdString();
        variable::Value value;

        /// @todo default.json нужно будет заменить на установленную палитру выделений
        variable::loadJson(path_to_highlights + "/" + params.highlight_data_file.toStdString(), value);

        if (value.type() == variable::ValueType::Object)
        {
            const variable::Value &light_value = value.getObject()->find(u"light");
            if (light_value.type() == variable::ValueType::Object)
                _highlight_data.light = light_value.getObject();

            const variable::Value &dark_value = value.getObject()->find(u"dark");
            if (dark_value.type() == variable::ValueType::Object)
                _highlight_data.dark = dark_value.getObject();
        }
    }

    return params;
}

void GeneralTextViewAdaptor::timerEvent(QTimerEvent *event)
{
    if (event->timerId() == _lsp_change_delay_timer)
        sendDidChangeNotification();

    if (event->timerId() == _autosave_change_delay_timer) {
        if (params().autosave) {
            killTimer(_autosave_change_delay_timer);
            _autosave_change_delay_timer = 0;
            document_adaptor()->saveFile(_path_to_file);
            markModification();
        }
        _shell.notifyContentChanged(_path_to_file);
    }
}

void GeneralTextViewAdaptor::sendDidChangeNotification()
{
    killTimer(_lsp_change_delay_timer);
    _lsp_change_delay_timer = 0;

    shell::LspAccess_interface *lsp = shell().getLspAccess("");
    if (lsp)
        lsp->didChange(path_to_file(), ++_version);
}